/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


/*
 ==============================================================================

 RENDER ENTITIES

 ==============================================================================
*/


/*
 ==================
 R_AddRenderEntity
 ==================
*/
int R_AddRenderEntity (scene_t *scene, const renderEntity_t *renderEntity){

	entity_t	*entity;

	if (scene->inUse)
		Com_Error(false, "R_AddRenderEntity: scene is currently in use");

	if (!scene->freeEntity){
		Com_Printf(S_COLOR_YELLOW "R_AddRenderEntity: no free entity slots\n");
		return -1;
	}

	entity = scene->freeEntity;

	// Fill it in
	entity->active = true;

	entity->e = *renderEntity;
	entity->instance = NULL;

	// Link
	scene->freeEntity = scene->freeEntity->next;

	entity->prev = &scene->activeEntity;
	entity->next = scene->activeEntity.next;

	scene->activeEntity.next->prev = entity;
	scene->activeEntity.next = entity;

	return entity->index;
}

/*
 ==================
 R_UpdateRenderEntity
 ==================
*/
void R_UpdateRenderEntity (scene_t *scene, int entityHandle, const renderEntity_t *renderEntity){

	entity_t	*entity;

	if (scene->inUse)
		Com_Error(false, "R_UpdateRenderEntity: scene is currently in use");

	if (entityHandle < 0 || entityHandle >= scene->maxEntities)
		Com_Error(false, "R_UpdateRenderEntity: entityHandle out of range");

	entity = &scene->entities[entityHandle];

	if (!entity->active){
		Com_Printf(S_COLOR_YELLOW "R_UpdateRenderEntity: entityHandle %i is not active\n", entityHandle);
		return;
	}

	if (r_skipUpdates->integerValue)
		return;

	tr.pc.entityUpdates++;

	// Clear instance
	R_ClearDynamicModelInstance(entity);

	// Update it
	entity->e = *renderEntity;
	entity->instance = NULL;
}

/*
 ==================
 R_FreeRenderEntity
 ==================
*/
void R_FreeRenderEntity (scene_t *scene, int entityHandle){

	entity_t	*entity;

	if (scene->inUse)
		Com_Error(false, "R_FreeRenderEntity: scene is currently in use");

	if (entityHandle < 0 || entityHandle >= scene->maxEntities)
		Com_Error(false, "R_FreeRenderEntity: entityHandle out of range");

	entity = &scene->entities[entityHandle];

	if (!entity->active){
		Com_Printf(S_COLOR_YELLOW "R_FreeRenderEntity: entityHandle %i is not active\n", entityHandle);
		return;
	}

	// Remove all decals
	R_RemoveDecals(scene, entity->index);

	// Clear instance
	R_ClearDynamicModelInstance(entity);

	// No longer active
	entity->active = false;

	// Unlink
	entity->prev->next = entity->next;
	entity->next->prev = entity->prev;

	entity->next = scene->freeEntity;
	scene->freeEntity = entity;
}


/*
 ==============================================================================

 RENDER LIGHTS

 ==============================================================================
*/


/*
 ==================
 R_AddRenderLight
 ==================
*/
int R_AddRenderLight (scene_t *scene, const renderLight_t *renderLight){

	light_t		*light;

	if (scene->inUse)
		Com_Error(false, "R_AddRenderLight: scene is currently in use");

	if (!scene->freeLight){
		Com_Printf(S_COLOR_YELLOW "R_AddRenderLight: no free light slots\n");
		return -1;
	}

	light = scene->freeLight;

	// Fill it in
	light->active = true;

	light->l = *renderLight;
	light->data.computed = false;

	// Link
	scene->freeLight = scene->freeLight->next;

	light->prev = &scene->activeLight;
	light->next = scene->activeLight.next;

	scene->activeLight.next->prev = light;
	scene->activeLight.next = light;

	return light->index;
}

/*
 ==================
 R_UpdateRenderLight
 ==================
*/
void R_UpdateRenderLight (scene_t *scene, int lightHandle, const renderLight_t *renderLight){

	light_t		*light;

	if (scene->inUse)
		Com_Error(false, "R_UpdateRenderLight: scene is currently in use");

	if (lightHandle < 0 || lightHandle >= scene->maxLights)
		Com_Error(false, "R_UpdateRenderLight: lightHandle out of range");

	light = &scene->lights[lightHandle];

	if (!light->active){
		Com_Printf(S_COLOR_YELLOW "R_UpdateRenderLight: lightHandle %i is not active\n", lightHandle);
		return;
	}

	if (r_skipUpdates->integerValue)
		return;

	tr.pc.lightUpdates++;

	// Update it
	light->l = *renderLight;
	light->data.computed = false;
}

/*
 ==================
 R_FreeRenderLight
 ==================
*/
void R_FreeRenderLight (scene_t *scene, int lightHandle){

	light_t		*light;

	if (scene->inUse)
		Com_Error(false, "R_FreeRenderLight: scene is currently in use");

	if (lightHandle < 0 || lightHandle >= scene->maxLights)
		Com_Error(false, "R_FreeRenderLight: lightHandle out of range");

	light = &scene->lights[lightHandle];

	if (!light->active){
		Com_Printf(S_COLOR_YELLOW "R_FreeRenderLight: lightHandle %i is not active\n", lightHandle);
		return;
	}

	// No longer active
	light->active = false;

	// Unlink
	light->prev->next = light->next;
	light->next->prev = light->prev;

	light->next = scene->freeLight;
	scene->freeLight = light;
}


/*
 ==============================================================================

 RENDER PARTICLES

 ==============================================================================
*/


/*
 ==================
 R_AddRenderParticle
 ==================
*/
int R_AddRenderParticle (scene_t *scene, const renderParticle_t *renderParticle){

	particle_t	*particle;

	if (scene->inUse)
		Com_Error(false, "R_AddRenderParticle: scene is currently in use");

	if (!scene->freeParticle){
		Com_Printf(S_COLOR_YELLOW "R_AddRenderParticle: no free particle slots\n");
		return -1;
	}

	particle = scene->freeParticle;

	// Fill it in
	particle->active = true;

	particle->p = *renderParticle;

	// Link
	scene->freeParticle = scene->freeParticle->next;

	particle->prev = &scene->activeParticle;
	particle->next = scene->activeParticle.next;

	scene->activeParticle.next->prev = particle;
	scene->activeParticle.next = particle;

	return particle->index;
}

/*
 ==================
 R_UpdateRenderParticle
 ==================
*/
void R_UpdateRenderParticle (scene_t *scene, int particleHandle, const renderParticle_t *renderParticle){

	particle_t	*particle;

	if (scene->inUse)
		Com_Error(false, "R_UpdateRenderParticle: scene is currently in use");

	if (particleHandle < 0 || particleHandle >= scene->maxParticles)
		Com_Error(false, "R_UpdateRenderParticle: particleHandle out of range");

	particle = &scene->particles[particleHandle];

	if (!particle->active){
		Com_Printf(S_COLOR_YELLOW "R_UpdateRenderParticle: particleHandle %i is not active\n", particleHandle);
		return;
	}

	if (r_skipUpdates->integerValue)
		return;

	tr.pc.particleUpdates++;

	// Update it
	particle->p = *renderParticle;
}

/*
 ==================
 R_FreeRenderParticle
 ==================
*/
void R_FreeRenderParticle (scene_t *scene, int particleHandle){

	particle_t	*particle;

	if (scene->inUse)
		Com_Error(false, "R_FreeRenderParticle: scene is currently in use");

	if (particleHandle < 0 || particleHandle >= scene->maxParticles)
		Com_Error(false, "R_FreeRenderParticle: particleHandle out of range");

	particle = &scene->particles[particleHandle];

	if (!particle->active){
		Com_Printf(S_COLOR_YELLOW "R_FreeRenderParticle: particleHandle %i is not active\n", particleHandle);
		return;
	}

	// No longer active
	particle->active = false;

	// Unlink
	particle->prev->next = particle->next;
	particle->next->prev = particle->prev;

	particle->next = scene->freeParticle;
	scene->freeParticle = particle;
}


/*
 ==============================================================================

 RENDER SCENES

 ==============================================================================
*/


/*
 ==================
 R_AllocRenderScene
 ==================
*/
scene_t *R_AllocRenderScene (int maxEntities, int maxLights, int maxParticles, int maxDecals){

	scene_t	*scene;
	int		i;

	// Find a free slot
	for (i = 0; i < MAX_RENDER_SCENES; i++){
		if (!tr.renderScenes[i])
			break;
	}

	if (i == MAX_RENDER_SCENES)
		Com_Error(false, "R_AllocRenderScene: MAX_RENDER_SCENES hit");

	// Allocate the scene
	tr.renderScenes[i] = scene = (scene_t *)Mem_Alloc(sizeof(scene_t), TAG_RENDERER);

	// Allow the scene to be modified
	scene->inUse = false;

	// Allocate and set up the entities
	scene->maxEntities = maxEntities + 1;
	if (scene->maxEntities > MAX_RENDER_ENTITIES)
		scene->maxEntities = MAX_RENDER_ENTITIES;

	if (scene->maxEntities)
		scene->entities = (entity_t *)Mem_ClearedAlloc(scene->maxEntities * sizeof(entity_t), TAG_RENDERER);
	else
		scene->entities = NULL;

	for (i = 0; i < scene->maxEntities; i++){
		scene->entities[i].index = i;

		if (i < scene->maxEntities - 1)
			scene->entities[i].next = &scene->entities[i+1];
	}

	// Allocate and set up the lights
	scene->maxLights = maxLights;
	if (scene->maxLights > MAX_RENDER_LIGHTS)
		scene->maxLights = MAX_RENDER_LIGHTS;

	if (scene->maxLights)
		scene->lights = (light_t *)Mem_ClearedAlloc(scene->maxLights * sizeof(light_t), TAG_RENDERER);
	else
		scene->lights = NULL;

	for (i = 0; i < scene->maxLights; i++){
		scene->lights[i].index = i;

		if (i < scene->maxLights - 1)
			scene->lights[i].next = &scene->lights[i+1];
	}

	// Allocate and set up the particles
	scene->maxParticles = maxParticles;
	if (scene->maxParticles > MAX_RENDER_PARTICLES)
		scene->maxParticles = MAX_RENDER_PARTICLES;

	if (scene->maxParticles)
		scene->particles = (particle_t *)Mem_ClearedAlloc(scene->maxParticles * sizeof(particle_t), TAG_RENDERER);
	else
		scene->particles = NULL;

	for (i = 0; i < scene->maxParticles; i++){
		scene->particles[i].index = i;

		if (i < scene->maxParticles - 1)
			scene->particles[i].next = &scene->particles[i+1];
	}

	// Allocate and set up the decals
	scene->maxDecals = maxDecals;
	if (scene->maxDecals > MAX_RENDER_DECALS)
		scene->maxDecals = MAX_RENDER_DECALS;

	if (scene->maxDecals)
		scene->decals = (decal_t *)Mem_ClearedAlloc(scene->maxDecals * sizeof(decal_t), TAG_RENDERER);
	else
		scene->decals = NULL;

	for (i = 0; i < scene->maxDecals - 1; i++)
		scene->decals[i].next = &scene->decals[i+1];

	// Set up the active lists
	scene->activeEntity.prev = &scene->activeEntity;
	scene->activeEntity.next = &scene->activeEntity;

	scene->activeLight.prev = &scene->activeLight;
	scene->activeLight.next = &scene->activeLight;

	scene->activeParticle.prev = &scene->activeParticle;
	scene->activeParticle.next = &scene->activeParticle;

	scene->activeDecal.prev = &scene->activeDecal;
	scene->activeDecal.next = &scene->activeDecal;

	// Set up the free lists
	scene->freeEntity = scene->entities;
	scene->freeLight = scene->lights;
	scene->freeParticle = scene->particles;
	scene->freeDecal = scene->decals;

	// Set up the world entity (all scenes will have one)
	scene->worldEntity = scene->freeEntity;

	// Fill it in
	scene->worldEntity->active = false;

	memset(&scene->worldEntity->e, 0, sizeof(renderEntity_t));
	scene->worldEntity->instance = NULL;

	scene->worldEntity->e.type = RE_MODEL;
	scene->worldEntity->e.model = NULL;
	scene->worldEntity->e.origin.Clear();
	scene->worldEntity->e.axis.Identity();
	scene->worldEntity->e.material = NULL;
	scene->worldEntity->e.materialParms[MATERIALPARM_RED] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_GREEN] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_BLUE] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_ALPHA] = 1.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_TIMEOFFSET] = 0.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_DIVERSITY] = 0.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_MISC] = 0.0f;
	scene->worldEntity->e.materialParms[MATERIALPARM_MODE] = 1.0f;

	// Link
	scene->freeEntity = scene->freeEntity->next;

	scene->worldEntity->prev = &scene->activeEntity;
	scene->worldEntity->next = scene->activeEntity.next;

	scene->activeEntity.next->prev = scene->worldEntity;
	scene->activeEntity.next = scene->worldEntity;

	// Clear the compiled lights list
	scene->compiledLights = false;
	memset(scene->compiledLightsList, 0, sizeof(scene->compiledLightsList));

	return scene;
}

/*
 ==================
 R_FreeRenderScene
 ==================
*/
void R_FreeRenderScene (scene_t *scene){

	entity_t		*entity;
	decal_t			*decal;
	compiledLight_t	*compiledLight;
	lightNode_t		*lightNode, *next;
	int				i;

	if (scene->inUse)
		Com_Error(false, "R_FreeRenderScene: scene is currently in use");

	// Free the slot
	for (i = 0; i < MAX_RENDER_SCENES; i++){
		if (tr.renderScenes[i] == scene){
			tr.renderScenes[i] = NULL;
			break;
		}
	}

	// If this scene was used as primaryView, set primaryViewAvailable to false
	if (tr.primaryViewAvailable && tr.primaryView.scene == scene)
		tr.primaryViewAvailable = false;

	// Make sure instances are cleared
	for (entity = scene->activeEntity.next; entity != &scene->activeEntity; entity = entity->next)
		R_ClearDynamicModelInstance(entity);

	// Make sure decal data is freed
	for (decal = scene->activeDecal.next; decal != &scene->activeDecal; decal = decal->next){
		if (decal->baseModel){
			Mem_Free(decal->baseTriangles);
			Mem_Free(decal->baseVertices);
		}

		Mem_Free(decal->indices);
		Mem_Free(decal->vertices);
	}

	// Make sure compiled lights are freed
	for (i = 0; i < MAX_ENTITIES; i++){
		if (!scene->compiledLightsList[i])
			continue;

		compiledLight = scene->compiledLightsList[i];

		for (lightNode = compiledLight->data.interactionNodes; lightNode; lightNode = next){
			next = lightNode->next;

			Mem_Free(lightNode->leafSurfaces);
			Mem_Free(lightNode);
		}

		for (lightNode = compiledLight->data.shadowNodes; lightNode; lightNode = next){
			next = lightNode->next;

			Mem_Free(lightNode->leafSurfaces);
			Mem_Free(lightNode);
		}

		Mem_Free(compiledLight);
	}

	// Free the scene
	Mem_Free(scene->entities);
	Mem_Free(scene->lights);
	Mem_Free(scene->particles);
	Mem_Free(scene->decals);

	Mem_Free(scene);
}

/*
 ==================
 R_RenderScene
 ==================
*/
void R_RenderScene (scene_t *scene, const renderView_t *renderView, bool primaryView){

	int		time;

	if (r_skipFrontEnd->integerValue)
		return;

	if (com_speeds->integerValue)
		time = Sys_Milliseconds();

	// Don't allow the scene to be modified until we're done with it
	scene->inUse = true;

	// If a primary view
	if (primaryView){
		if (!tr.world)
			Com_Error(false, "R_RenderScene: NULL world");

		// Save the primary view
		tr.primaryViewAvailable = true;

		tr.primaryView.scene = scene;
		tr.primaryView.renderView = *renderView;
	}

	// Set up viewParms
	tr.viewParms.primaryView = primaryView;
	tr.viewParms.viewType = VIEW_MAIN;

	tr.viewParms.portalSkyVisible = false;
	tr.viewParms.portalSkyMaterial = NULL;
	tr.viewParms.portalSkyEntity = NULL;

	// Copy scene
	tr.scene = scene;

	// Copy renderView
	tr.renderView = *renderView;

	// Render main view
	R_RenderView();

	if (com_speeds->integerValue)
		com_timeFrontEnd += (Sys_Milliseconds() - time);
}
